﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace Lilium.RetailAudit.Data
{
	public sealed class EntityComparer<TEntity> : IEqualityComparer<TEntity>
	{
		public bool IsNullOrDefault(TEntity entity)
		{
			return (entity == null) || Equals(entity, DefaultInstance);
		}

		#region public static EntityComparer<TEntity> Default

		private readonly static EntityComparer<TEntity> m_Default = new EntityComparer<TEntity>();

		public static EntityComparer<TEntity> Default
		{
			[DebuggerNonUserCodeAttribute]
			get
			{
				return m_Default;
			}
		}

		#endregion

		#region IEqualityComparer<TEntity> Members

		public bool Equals(TEntity x, TEntity y)
		{
			return EqualsFunc(x, y);
		}

		public int GetHashCode(TEntity obj)
		{
			return GetHashCodeFunc(obj);
		}

		#endregion

		#region private static Func<TEntity, TEntity, bool> EqualsFunc

		private readonly static Func<TEntity, TEntity, bool> m_EqualsFunc = CreateEqualsFunc();

		private static Func<TEntity, TEntity, bool> CreateEqualsFunc()
		{
			var entityType = typeof(TEntity);
			var filedComparers = new List<Func<TEntity, TEntity, bool>>();
			foreach (var propertyInfo in entityType.GetProperties())
			{
				var context = new { PropertyInfo = propertyInfo };
				filedComparers.Add((x, y) =>
				{
					var xValue = context.PropertyInfo.GetValue(x, null);
					var yValue = context.PropertyInfo.GetValue(y, null);
					return object.Equals(xValue, yValue);
				});
			}
			return (x, y) =>
			{
				if (object.ReferenceEquals(x, y))
					return true;
				else
				{
					if ((x == null) || (y == null))
						return false;
					else
					{
						foreach (var fieldComaprer in filedComparers)
							if (!fieldComaprer(x, y))
								return false;
						return true;
					}
				}
			};
		}

		private static Func<TEntity, TEntity, bool> EqualsFunc
		{
			[DebuggerNonUserCodeAttribute]
			get
			{
				return m_EqualsFunc;
			}
		}

		#endregion

		#region private static Func<TEntity, bool> GetHashCodeFunc

		private static readonly Func<TEntity, int> m_GetHashCodeFunc = CerateGetHashCodeFunc();

		private static Func<TEntity, int> CerateGetHashCodeFunc()
		{
			var entityType = typeof(TEntity);
			var hashExtractors = new List<Func<TEntity, int>>();
			foreach (var propertyInfo in entityType.GetProperties())
				hashExtractors.Add(obj =>
				{
					var propValue = propertyInfo.GetValue(obj, null);
					if (propValue != null)
						return propValue.GetHashCode();
					else
						return 0;
				});
			return obj =>
			{
				var hashCode = 0;
				if (obj == null)
					foreach (var hashExtractor in hashExtractors)
						hashCode ^= hashExtractor(obj);
				return hashCode;
			};
		}

		private static Func<TEntity, int> GetHashCodeFunc
		{
			[DebuggerNonUserCodeAttribute]
			get
			{
				return m_GetHashCodeFunc;
			}
		}

		#endregion

		#region private static TEntity DefaultInstance

		private readonly static TEntity m_DefaultInstance = Activator.CreateInstance<TEntity>();

		private static TEntity DefaultInstance
		{
			[DebuggerNonUserCodeAttribute]
			get
			{
				return m_DefaultInstance;
			}
		}

		#endregion
	}
}